package cn.net.mysoft.account.svr.impl;


import cn.net.mysoft.account.api.common.AlgInNeed;
import cn.net.mysoft.account.api.common.DateTimeTool;
import cn.net.mysoft.account.api.constant.AccountConstant;
import cn.net.mysoft.account.api.entity.AccountDetailEntity;
import cn.net.mysoft.account.api.entity.AccountEntity;
import cn.net.mysoft.account.api.exception.TransactionException;
import cn.net.mysoft.account.api.protocol.RefundInfo;
import cn.net.mysoft.account.api.protocol.TransferInfo;
import cn.net.mysoft.account.api.service.AccountService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.text.SimpleDateFormat;
import java.util.*;


@RestController
@Transactional
@RequestMapping("/account")
public class AccountServiceImpl implements AccountService {
    @Resource
    private AccountDao objAccountDao;
    @Resource
    private AcountDetailDao objAcountDetailDao;

    private Logger logger = LoggerFactory.getLogger(this.getClass());

    @Override
    public int createControlAccount(String uid){

        if(!uid.startsWith(AccountConstant.UID_FOR_CONTROL_ACCOUNT)) return 0;

        AccountEntity objAcc = new AccountEntity();

        objAcc.setUserid(uid);
        objAcc.setBalance(0L);
        objAcc.setRemark("");
        objAcc.setState(AccountConstant.STATE_NORMAL);
        objAcc.setType(AccountConstant.TYPE_CONTROL_ACCOUNT);
        objAcc.setModifyTime(getCurrentTimeString());
        objAcc.setCreateTime(objAcc.getModifyTime());
        objAcc.setVer(0);
        objAcc.setSign();

        //System.out.println(objAcc.toSignString());

        return objAccountDao.insert(objAcc);
    }

    @Override
    public int createAccount(String uid) {

        if(uid.startsWith(AccountConstant.UID_FOR_CONTROL_ACCOUNT)) return createControlAccount(uid);

        AccountEntity objAcc = new AccountEntity();

        objAcc.setUserid(uid);
        objAcc.setBalance(0L);
        objAcc.setRemark("");
        objAcc.setState(AccountConstant.STATE_NORMAL);
        objAcc.setType(AccountConstant.TYPE_NORMAL_ACCOUNT);
        objAcc.setModifyTime(getCurrentTimeString());
        objAcc.setCreateTime(objAcc.getModifyTime());
        objAcc.setVer(0);
        objAcc.setSign();

        return objAccountDao.insert(objAcc);
    }

    @Override
    public long getBalance(String uin) {
        AccountEntity objAccountEntity = objAccountDao.getByUserId(uin);

        // 没有查询到
        if(objAccountEntity == null) return 0;

        // 记录存在手工修改
        if(!objAccountEntity.isValid()) return 0;

        //System.out.println(objAccountEntity.toSignString());

        return objAccountEntity.getBalance();
    }
    private boolean checkReplay(String vno, String from, String to, long amt, int type, String desc){
        logger.info("{} {} {} {} {} {} ",
                vno,from,to,amt,type,desc);
        // inquire flow
        AccountDetailEntity fromItem = objAcountDetailDao.getAccoutDetailByVno(from,vno);
        AccountDetailEntity toItem = objAcountDetailDao.getAccoutDetailByVno(to,vno);


        if(toItem == null ){
            logger.info("fromItem detail is null {}",from);
            return false;
        }
        if(toItem == null ){
            logger.info("toItem detail is null {}",to);
            return false;
        }

        logger.info("{} {} {} {}  ",
                fromItem.getAmount(),toItem.getAmount(),fromItem.getType(),toItem.getType());

        if((fromItem.getAmount() == amt) &&
                (toItem.getAmount() == amt) &&
                (fromItem.getType() == type) &&
                (toItem.getType() == type)
        ) {
            return true;
        }
        return false;
    }
    @Override
    public int transfer(TransferInfo itm){
        int retcd = 0;
        try {
            retcd = transfer(itm.getVno(), itm.getFrom(), itm.getTo(), itm.getAmt(), itm.getType(), itm.getDesc());
        }catch (TransactionException exp){
            retcd = exp.getErrorCode();
            logger.error(exp.getMessage());
        }
        return retcd;
    }
    /** 仅仅只能在普通账户上实现转账
     * return :
     *  -1 系统故障
     *  0 success
     *  1 超出限额
     *  2 余额不足
     *  3 from账户不存在
     *  4 to账户不存在
     *
     */
    private synchronized int transfer(String vno, String from, String to, Long amt, int type, String desc) throws TransactionException {
        int res = 0;
        // 检查是否重放交易，如果是直接回复成功
        if(checkReplay(vno,from,to ,amt,type,desc)) return 0;

        // 检查双方的余额是否可进行交易 0, Integer.MAX_VALUE
        AccountEntity fromAcc = objAccountDao.getByUserId(from);
        AccountEntity toAcc = objAccountDao.getByUserId(to);
        if(fromAcc == null ) {
            createAccount(from);fromAcc = objAccountDao.getByUserId(from);
            if(fromAcc == null )  throw new TransactionException(1003,"From account not exist :"+from) ;
        }
        if(toAcc == null ) {
            createAccount(to);toAcc = objAccountDao.getByUserId(to);
            if(toAcc == null )throw new TransactionException(1004,"To account not exist :"+to) ;
        }

        // 检查余额
        long fromAccAfterBalance = checkBalance(fromAcc,amt,AccountConstant.FLAG_DECREASE);
        long toAccAfterBalance = checkBalance(toAcc,amt,AccountConstant.FLAG_INCREASE);
        if (fromAccAfterBalance < 0 ) throw new TransactionException(1002,"From account balance not enough:"+from) ;
        if (toAccAfterBalance < 0 ) throw new TransactionException(1001,"Exceed To account balance limit:"+to) ;

        // 记录流水
        AccountDetailEntity detailOfFrom = new AccountDetailEntity();
        AccountDetailEntity detailOfTo = new AccountDetailEntity();

        detailOfFrom.setAmount(amt);
        detailOfFrom.setUserId(from);
        detailOfFrom.setBalanceAfter(fromAccAfterBalance);
        detailOfFrom.setBalanceBefore(fromAcc.getBalance());
        detailOfFrom.setDirection(AccountConstant.FLAG_DECREASE);
        detailOfFrom.setOpponent(to);
        detailOfFrom.setType(type);
        detailOfFrom.setRemark(desc);
        detailOfFrom.setVno(vno);
        detailOfFrom.setCreateTime(getCurrentTimeString());
        // need to be signed to guard mt data
        detailOfFrom.setSign(AlgInNeed.shaEncode(detailOfFrom.toSignString()));


        detailOfTo.setAmount(amt);
        detailOfTo.setUserId(to);
        detailOfTo.setBalanceAfter(toAccAfterBalance);
        detailOfTo.setBalanceBefore(toAcc.getBalance());
        detailOfTo.setDirection(AccountConstant.FLAG_INCREASE);
        detailOfTo.setOpponent(from);
        detailOfTo.setType(type);
        detailOfTo.setRemark(desc);
        detailOfTo.setVno(vno);
        detailOfTo.setCreateTime(getCurrentTimeString());
        // need to be signed to guard mt data
        detailOfTo.setSign(AlgInNeed.shaEncode(detailOfTo.toString()));



        // 修改余额
        fromAcc.setModifyTime(getCurrentTimeString());
        fromAcc.setBalance(fromAccAfterBalance);
        fromAcc.setSign(AlgInNeed.shaEncode(fromAcc.toSignString()));

        toAcc.setModifyTime(getCurrentTimeString());
        toAcc.setBalance(toAccAfterBalance);
        toAcc.setSign(AlgInNeed.shaEncode(toAcc.toSignString()));


        res = objAccountDao.updateBalance(fromAcc); if(res != 1) {throw new TransactionException("Failed in Updating FromAccount Balance!" + fromAcc.getUserId());}
        res = objAcountDetailDao.insert(detailOfFrom);if(res != 1) {throw new TransactionException("Failed in inserting FromAccount detail!" + fromAcc.getUserId());}
        res = objAccountDao.updateBalance(toAcc);if(res != 1) {throw new TransactionException("Failed in Updating ToAccount Balance!" + toAcc.getUserId());}
        res = objAcountDetailDao.insert(detailOfTo);if(res != 1) {throw new TransactionException("Failed in inserting TOccount detail!" + toAcc.getUserId());}


        return 0;
    }


    /**
     *
     * @param itm
     * @param amt
     * @param dir 0 inc 1 dec
     * @return -1 失败；其他是减少后的余额值
     */
    private long checkBalance(AccountEntity itm,long amt,int dir){
        long res = -1;

        switch (itm.getType()){
            //  普通账户
            case AccountConstant.TYPE_NORMAL_ACCOUNT: {
                if(dir == AccountConstant.FLAG_INCREASE){
                    res = ((Integer.MAX_VALUE - itm.getBalance()) <= amt) ? -1:(itm.getBalance()+amt);
                }else if(dir == AccountConstant.FLAG_DECREASE){
                    res = (itm.getBalance() < amt)?-1:(itm.getBalance() - amt);
                }
            }break;
            // 控制账户
            case AccountConstant.TYPE_CONTROL_ACCOUNT:{
                if(dir == AccountConstant.FLAG_DECREASE){
                    res = ((Integer.MAX_VALUE - itm.getBalance()) <= amt) ? -1:(itm.getBalance()+amt);
                }else if(dir == AccountConstant.FLAG_INCREASE){
                    res = (itm.getBalance() < amt)?-1:(itm.getBalance() - amt);
                }
            }break;
        }
        return res;
    }

    @Override
    public int reverse(TransferInfo itm){
        int retcd = 0;
        try {
            retcd = reverse(itm.getVno(), itm.getFrom(), itm.getTo(), itm.getAmt(), itm.getType(), itm.getDesc());
        }catch (TransactionException exp){
            retcd = exp.getErrorCode();
            logger.error(exp.getMessage());
        }
        return retcd;
    }

    @Override
    /**取消转账的交易，先核实原交易是否存在，如果存在则执行反向操作，VNO+_V_+RVNO
     *
     * ret
     *  0 成功
     *  1 from不存在对应的流水
     *  2 to 不存在对应的流水
     *  3 amt不一致
     *  4 方向不一样
     *  5 历史存在from与to退款金额不一致
     *  6 历史存在方向不对
     *  7 退款单已经存在，且信息与本次不一样
     *  8 累计退款超过原订单金额
     */
    public synchronized int refund(RefundInfo itm) {
        // check flow
        AccountDetailEntity fromItem = objAcountDetailDao.getAccoutDetailByVno(itm.getFrom(),itm.getVno());
        AccountDetailEntity toItem = objAcountDetailDao.getAccoutDetailByVno(itm.getTo(),itm.getVno());
        // 检查vno，amt，dir
        if(fromItem == null) return 2001;
        if(toItem == null) return 2002;
        if((fromItem.getAmount() < itm.getRefundAmt()) || (toItem.getAmount() != fromItem.getAmount()  )) return 2003;
        if((fromItem.getDirection() != AccountConstant.FLAG_DECREASE) || (toItem.getDirection() != AccountConstant.FLAG_INCREASE)) return 2004;
        // 查看已经退款的流水，并确认本次退款的金额，小于原流水单金额
        List<AccountDetailEntity> fromItemRefundList = objAcountDetailDao.getRefundAccountDetailByVno(itm.getFrom(),itm.getVno());
        List<AccountDetailEntity> toItemRefundList = objAcountDetailDao.getRefundAccountDetailByVno(itm.getTo(),itm.getVno());
        if(fromItemRefundList.size() != toItemRefundList.size()) return 2005;

        long amtAlreadyRefund = 0;
        Map<String,AccountDetailEntity> mapVnoFromDE = new HashMap<>();
        for(AccountDetailEntity itmDE:fromItemRefundList){
            if(itmDE.getDirection() != AccountConstant.FLAG_INCREASE) return 2006;
            mapVnoFromDE.put(itmDE.getVno(),itmDE);
            amtAlreadyRefund += itmDE.getAmount();
        }
        for(AccountDetailEntity itmDE:toItemRefundList){
            if(itmDE.getDirection() != AccountConstant.FLAG_DECREASE) return 2006;
            if(!mapVnoFromDE.containsKey(itmDE.getVno())) return 2005;
            if(itmDE.getAmount() != mapVnoFromDE.get(itmDE.getVno()).getAmount()) return 2005;
        }
        // 检查退款段是否存在
        String refundVno = itm.getVno() + "_V_"+itm.getRefundVno();
        if(mapVnoFromDE.containsKey(refundVno)){
            AccountDetailEntity fde = mapVnoFromDE.get(refundVno);
            if((itm.getRefundAmt() == fde.getAmount()) &&
                    (itm.getFrom().compareTo(fde.getUserId()) == 0) &&
                    (itm.getTo().compareTo(fde.getOpponent()) == 0)
            ) return 0;
            else return 2007;
        }else{
            if(fromItem.getAmount()  < amtAlreadyRefund + itm.getRefundAmt()) return 2008;
        }
        // 执行退款
        TransferInfo tinfo = new TransferInfo();
            tinfo.setType(AccountConstant.TYPE_REFUND);
            tinfo.setTo(itm.getFrom());
            tinfo.setFrom(itm.getTo());
            tinfo.setAmt(itm.getRefundAmt());
            tinfo.setDesc(itm.getDesc());

            tinfo.setVno(itm.getVno() + "_V_" + itm.getRefundVno());

        return transfer(tinfo) ;
    }

    /**取消转账的交易，先核实原交易是否存在，如果存在则执行反向操作，VNO+_R
     *
     * ret
     *  0 成功
     *  1 from不存在对应的流水
     *  2 to 不存在对应的流水
     *  3 amt不一致
     *  4 方向不一样
     *  5 存在退款
     */
    public int reverse(String vno, String from, String to, long amt, int type, String desc) throws TransactionException {
        // inquire flow
        AccountDetailEntity fromItem = objAcountDetailDao.getAccoutDetailByVno(from,vno);
        AccountDetailEntity toItem = objAcountDetailDao.getAccoutDetailByVno(to,vno);

        // 检查vno，amt，dir
        if(fromItem == null) return 2001;
        if(toItem == null) return 2002;

        if((fromItem.getAmount() != amt) || (toItem.getAmount() != amt)||(fromItem.getAmount() != toItem.getAmount())) return 2003;

        if((fromItem.getDirection() != AccountConstant.FLAG_DECREASE) || (toItem.getDirection() != AccountConstant.FLAG_INCREASE)) return 2004;

        // 检查是否存在退款
        List<AccountDetailEntity> fromItemRefundList = objAcountDetailDao.getRefundAccountDetailByVno(from,vno);
        List<AccountDetailEntity> toItemRefundList = objAcountDetailDao.getRefundAccountDetailByVno(to,vno);
        if((fromItemRefundList.size() != toItemRefundList.size()) || fromItemRefundList.size()>0) return 2005;

        // vno + R
        String vnoReverse = vno + "_R";

        return transfer(vnoReverse,to,from,amt,type,desc);
    }

    @Override
    public List<AccountDetailEntity> getAccountDetail(String uin, String btime, String etime, int offset, int count) {
        if(uin == null ) return null;

        if(btime == null) btime = DateTimeTool.getBeforeDateTime(86400);
        if(etime == null) etime = DateTimeTool.getCurrentDateTime();

        logger.info(uin,btime,etime,offset,count);

        return objAcountDetailDao.getAccountDetail(uin,btime,etime,offset,count);
    }

    private String getCurrentTimeString() {
        String formats = "yyyy-MM-dd HH:mm:ss";
        Date d = new Date();
        SimpleDateFormat sdf = new SimpleDateFormat(formats,Locale.CHINA);
        return sdf.format(d);
    }
}
